// kernel.cc 
//	Initialization and cleanup routines for the Nachos kernel.
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "debug.h"
#include "main.h"
#include "kernel.h"
#include "sysdep.h"
#include "synch.h"
#include "synchlist.h"
#include "libtest.h"
#include "string.h"
#include "synchconsole.h"
#include "synchdisk.h"
#include "post.h"
#include <stdlib.h>

#include "threadtest.h"		//added by tangayfeng@seedclass 3-26-2011
//----------------------------------------------------------------------
// Kernel::Kernel
// 	Interpret command line arguments in order to determine flags 
//	for the initialization (see also comments in main.cc)  
//----------------------------------------------------------------------

Kernel::Kernel(int argc, char **argv) {
	randomSlice = FALSE;
	debugUserProg = FALSE;
	consoleIn = NULL; // default is stdin
	consoleOut = NULL; // default is stdout
#ifndef FILESYS_STUB
	formatFlag = FALSE;
#endif
	reliability = 1; // network reliability, default is 1.0
	hostName = 0; // machine id, also UNIX socket name
	// 0 is the default machine id
	for (int i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-rs") == 0) {
			ASSERT(i + 1 < argc);
			RandomInit(atoi(argv[i + 1]));// initialize pseudo-random
			// number generator
			randomSlice = TRUE;
			i++;
		} else if (strcmp(argv[i], "-s") == 0) {
			debugUserProg = TRUE;
		} else if (strcmp(argv[i], "-ci") == 0) {
			ASSERT(i + 1 < argc);
			consoleIn = argv[i + 1];
			i++;
		} else if (strcmp(argv[i], "-co") == 0) {
			ASSERT(i + 1 < argc);
			consoleOut = argv[i + 1];
			i++;
#ifndef FILESYS_STUB
		} else if (strcmp(argv[i], "-f") == 0) {
			formatFlag = TRUE;
#endif
		} else if (strcmp(argv[i], "-n") == 0) {
			ASSERT(i + 1 < argc); // next argument is float
			reliability = atof(argv[i + 1]);
			i++;
		} else if (strcmp(argv[i], "-m") == 0) {
			ASSERT(i + 1 < argc); // next argument is int
			hostName = atoi(argv[i + 1]);
			i++;
		} else if (strcmp(argv[i], "-u") == 0) {
			cout << "Partial usage: nachos [-rs randomSeed]\n";
			cout << "Partial usage: nachos [-s]\n";
			cout << "Partial usage: nachos [-ci consoleIn] [-co consoleOut]\n";
#ifndef FILESYS_STUB
			cout << "Partial usage: nachos [-nf]\n";
#endif
			cout << "Partial usage: nachos [-n #] [-m #]\n";
		}
	}
}

//----------------------------------------------------------------------
// Kernel::Initialize
// 	Initialize Nachos global data structures.  Separate from the 
//	constructor because some of these refer to earlier initialized
//	data via the "kernel" global variable.
//----------------------------------------------------------------------

void Kernel::Initialize() {
	// We didn't explicitly allocate the current thread we are running in.
	// But if it ever tries to give up the CPU, we better have a Thread
	// object to save its state.
	currentThread = new Thread("main");
	currentThread->setStatus(RUNNING);
	currentThread->SetPriority(0);
	systemThreadList = new List<Thread *> ;
	userProgramSpaceStateStack = new Stack<AddrSpace *> (NumTotalUserProgam);

	stats = new Statistics(); // collect statistics
	interrupt = new Interrupt; // start up interrupt handling
	scheduler = new Scheduler(); // initialize the ready queue
	alarm = new Alarm(randomSlice); // start up time slicing
	machine = new Machine(debugUserProg);
	synchConsoleIn = new SynchConsoleInput(consoleIn); // input from stdin
	synchConsoleOut = new SynchConsoleOutput(consoleOut); // output to stdout
	synchDisk = new SynchDisk(); //
	Mmbmp = new Bitmap(NumPhysPages); // init bitmap with NumPhysPages=128
#ifdef FILESYS_STUB
	fileSystem = new FileSystem();
#else
	fileSystem = new FileSystem(formatFlag);
#endif // FILESYS_STUB
	postOfficeIn = new PostOfficeInput(10);
	postOfficeOut = new PostOfficeOutput(reliability);

	interrupt->Enable();
}

//----------------------------------------------------------------------
// Kernel::~Kernel
// 	Nachos is halting.  De-allocate global data structures.
//----------------------------------------------------------------------

Kernel::~Kernel() {
	delete stats;
	delete systemThreadList;
	delete interrupt;
	delete scheduler;
	delete alarm;
	delete machine;
	delete synchConsoleIn;
	delete synchConsoleOut;
	delete synchDisk;
	delete fileSystem;
	delete postOfficeIn;
	delete postOfficeOut;
	delete Mmbmp;

	Exit(0);
}

//----------------------------------------------------------------------
// Kernel::saveProcessState
//
//		save the Process State
//			--save registers
//			--save user program space
//----------------------------------------------------------------------
bool Kernel::saveProcessState() {
	if (!kernel->machine->SaveRegisterState()) {
		DEBUG(dbgSys, "Save Registers Failed, thread name: " <<
				kernel->currentThread->getName() << "\n");
		return false;
	}
	if (!kernel->userProgramSpaceStateStack->Push(kernel->currentThread->space)) {
		DEBUG(dbgSys, "Save Program Space Failed, thread name: " <<
				kernel->currentThread->getName() << "\n");
		return false;
	}
	return true;
}

//----------------------------------------------------------------------
// Kernel::restoreProcessState
//
//		restore the Process State
//			--restore registers
//			--restore user program space
//----------------------------------------------------------------------
bool Kernel::restoreProcessState() {
	if (!kernel->machine->RestoreRegisterState()) {
		DEBUG(dbgSys, "Restore Registers Failed, thread name: " <<
				kernel->currentThread->getName() << "\n");
		return false;
	}
	kernel->currentThread->space = kernel->userProgramSpaceStateStack->Pop();

	/*
	 * One night fight, but without this operate...Everything is
	 * 		cloud.
	 * So...I just waste one night...working on this
	 */
	kernel->currentThread->space->RestoreState();
	return true;
}

//----------------------------------------------------------------------
// Kernel::ThreadSelfTest
//      Test threads, semaphores, synchlists
//
//	modified by tangayfeng@seedclass 2-23-2011
//----------------------------------------------------------------------

void Kernel::ThreadSelfTest() {

	ThreadTestStruct* tts = new ThreadTestStruct;

	Semaphore *semaphore;
	SynchList<int> *synchList;

	if (!ReadThreadTestPara(scheduler, "TestThread.conf", tts)) {
		// lol~ this is just for debug~
		std::cout << "Read TestThread Failed!" << std::endl;
	}

	if (0) {
		LibSelfTest(); // test library routines
		// modified by tangyafeng@seedclass
		// no-need to test 3-26-2011
	}

	currentThread->SelfTest(tts); // test thread switching

	// test semaphore operation
	semaphore = new Semaphore("test", 0);
	semaphore->SelfTest();
	delete semaphore;

	// test locks, condition variables
	// using synchronized lists
	synchList = new SynchList<int> ;
	synchList->SelfTest(9);
	delete synchList;

}

//----------------------------------------------------------------------
// Kernel::ConsoleTest
//      Test the synchconsole
//----------------------------------------------------------------------

void Kernel::ConsoleTest() {

	char ch;

	cout << "Testing the console device.\n"
			<< "Typed characters will be echoed, until ^D is typed.\n"
			<< "Note newlines are needed to flush input through UNIX.\n";
	cout.flush();

	do {
		ch = synchConsoleIn->GetChar();
		if (ch != EOF)
			synchConsoleOut->PutChar(ch); // echo it!
	} while (ch != EOF);

	cout << "\n";

}

//----------------------------------------------------------------------
// Kernel::NetworkTest
//      Test whether the post office is working. On machines #0 and #1, do:
//
//      1. send a message to the other machine at mail box #0
//      2. wait for the other machine's message to arrive (in our mailbox #0)
//      3. send an acknowledgment for the other machine's message
//      4. wait for an acknowledgement from the other machine to our 
//          original message
//
//  This test works best if each Nachos machine has its own window
//----------------------------------------------------------------------

void Kernel::NetworkTest() {

	if (hostName == 0 || hostName == 1) {
		// if we're machine 1, send to 0 and vice versa
		int farHost = (hostName == 0 ? 1 : 0);
		PacketHeader outPktHdr, inPktHdr;
		MailHeader outMailHdr, inMailHdr;
		char *data = "Hello there!";
		char *ack = "Got it!";
		char buffer[MaxMailSize];

		// construct packet, mail header for original message
		// To: destination machine, mailbox 0
		// From: our machine, reply to: mailbox 1
		outPktHdr.to = farHost;
		outMailHdr.to = 0;
		outMailHdr.from = 1;
		outMailHdr.length = strlen(data) + 1;

		// Send the first message
		postOfficeOut->Send(outPktHdr, outMailHdr, data);

		// Wait for the first message from the other machine
		postOfficeIn->Receive(0, &inPktHdr, &inMailHdr, buffer);
		cout << "Got: " << buffer << " : from " << inPktHdr.from << ", box "
				<< inMailHdr.from << "\n";
		cout.flush();

		// Send acknowledgement to the other machine (using "reply to" mailbox
		// in the message that just arrived
		outPktHdr.to = inPktHdr.from;
		outMailHdr.to = inMailHdr.from;
		outMailHdr.length = strlen(ack) + 1;
		postOfficeOut->Send(outPktHdr, outMailHdr, ack);

		// Wait for the ack from the other machine to the first message we sent
		postOfficeIn->Receive(1, &inPktHdr, &inMailHdr, buffer);
		cout << "Got: " << buffer << " : from " << inPktHdr.from << ", box "
				<< inMailHdr.from << "\n";
		cout.flush();
	}

	// Then we're done!
}

void Kernel::SemaphoreTest() {

}

